package sunwul.mall.auth.config;

import cn.hutool.json.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import sunwul.mall.auth.impl.UserDetailsServiceImpl;
import sunwul.mall.auth.model.LoginResult;
import sunwul.mall.commonbase.constant.AuthConstants;
import sunwul.mall.commonbase.constant.BusinessStatusEnum;
import sunwul.mall.commonbase.constant.HttpConstants;
import sunwul.mall.commonbase.model.Result;

import java.io.PrintWriter;
import java.time.Duration;
import java.util.UUID;

/**
 * @author sunwul
 * @date 2024/12/26 10:21
 * @description Security安全框架配置类
 */
@Configuration
public class AuthSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 自定义认证流程
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 关闭跨站伪造
        http.cors().disable();
        // 关闭跨域请求
        http.csrf().disable();
        // 关闭session使用策略
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 配置登录信息
        http.formLogin().loginProcessingUrl(AuthConstants.LOGIN_URL) // 登录url
                .successHandler(authenticationSuccessHandler()) // 登录成功处理
                .failureHandler(authenticationFailureHandler()); // 登录失败处理

        // 配置登出信息
        http.logout().logoutUrl(AuthConstants.LOGOUT_URL) // 登出url
                .logoutSuccessHandler(logoutSuccessHandler()); // 登出成功处理

        // 所有请求都需要进行身份认证
        http.authorizeHttpRequests().anyRequest().authenticated();
    }

    /**
     * 登录成功处理器
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (request, response, authentication) -> {
            response.setContentType(HttpConstants.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstants.UTF_8);

            // token
            String token = UUID.randomUUID().toString();
            // 从Security框架中获取认证用户对象(SecurityUser)
            String userJsonStr = new JsonMapper().writeValueAsString(authentication.getPrincipal());
            // token 为key, 认证用户对象的JSON字符串为value, 存入redis
            stringRedisTemplate.opsForValue().set(AuthConstants.LOGIN_TOKEN_PREFIX + token, userJsonStr, Duration.ofSeconds(AuthConstants.TOKEN_TIME));

            // 返回统一响应信息
            LoginResult loginResult = new LoginResult(token, AuthConstants.TOKEN_TIME);
            Result<Object> result = Result.success(loginResult);
            String s = new ObjectMapper().writeValueAsString(result);
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.flush();
            writer.close();
        };
    }

    /**
     * 登录失败处理器
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, authenticationException) -> {
            response.setContentType(HttpConstants.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstants.UTF_8);

            Result<Object> result = new Result<>();
            result.setCode(BusinessStatusEnum.FAIL.getCode());

            if (authenticationException instanceof BadCredentialsException) {
                result.setMsg("用户名或密码错误!");
            } else if (authenticationException instanceof UsernameNotFoundException) {
                result.setMsg("用户不存在!");
            } else if (authenticationException instanceof AccountExpiredException) {
                result.setMsg("账号异常, 请联系管理员!");
            } else if (authenticationException instanceof AccountStatusException) {
                result.setMsg("账号异常, 请联系管理员!");
            } else if (authenticationException instanceof InternalAuthenticationServiceException) {
                result.setMsg(authenticationException.getMessage());
            } else {
                result.setMsg(BusinessStatusEnum.FAIL.getDesc());
            }
            // 返回结果
            String s = new ObjectMapper().writeValueAsString(result);
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.flush();
            writer.close();
        };
    }

    /**
     * 登出成功处理器
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return (request, response, authentication) -> {
            response.setContentType(HttpConstants.APPLICATION_JSON);
            response.setCharacterEncoding(HttpConstants.UTF_8);

            // 从请求头中获取token
            String authorization = request.getHeader(AuthConstants.AUTHORIZATION);
            String token = authorization.replaceFirst(AuthConstants.BEARER, "");
            // 移除redis中的token
            stringRedisTemplate.delete(AuthConstants.LOGIN_TOKEN_PREFIX + token);

            Result<Object> result = Result.success(null);
            // 返回结果
            String s = new ObjectMapper().writeValueAsString(result);
            PrintWriter writer = response.getWriter();
            writer.write(s);
            writer.flush();
            writer.close();
        };
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
